------------------------------------------------------------
-- GearManagerEx.lua
--
-- Abin
-- 2009-6-10
------------------------------------------------------------

local L = GEARMANAGEREX_LOCALE -- Locale table
local VERSION = GetAddOnMetadata("GearManagerEx", "Version") or "1.0"
local BANKS = { -1, 5, 6, 7, 8, 9, 10, 11 } -- Bag id's for bank
local BAGS = { 0, 1, 2, 3, 4 } -- Bag id's for container

local db = { talentBind = {}, stripped = {} } -- Mapped to GearManagerExDB
local activeId, activeSet, bankOpened, rClickedSet, rClickedSetIcon, prevDropDown, inputDialog

-- Checked icon frame
local checkFrame = CreateFrame("Frame", "GearManagerExCheckFrame", GearManagerDialog)
local checkIcon = checkFrame:CreateTexture("GearManagerExCheckFrameIcon", "OVERLAY")
checkIcon:SetAllPoints(checkFrame)
checkIcon:SetTexture("Interface\\RaidFrame\\ReadyCheck-Ready")
checkFrame:Hide()
checkFrame:SetFrameStrata("FULLSCREEN_DIALOG")
checkFrame:SetWidth(28)
checkFrame:SetHeight(28)

--Prints a message
local function PrintMessage(msg, ...)
	DEFAULT_CHAT_FRAME:AddMessage("|cffffff78GearManagerEx:|r "..tostring(msg), ...)
end

-- Create the input dialog(on-demand)
local function CreateInputDialog(name, parent)

	local frame = CreateFrame("Frame", name, parent)
	frame:Hide()
	frame:SetWidth(280)
	frame:SetHeight(120)
	frame:SetPoint("CENTER")
	frame:SetBackdrop({ bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background", tile = true, tileSize = 32, edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border", edgeSize = 32, insets = {left = 11, right = 12, top = 12, bottom = 11 }, })
	frame:SetToplevel(true)
	frame:EnableMouse(true)
	frame:SetMovable(true)
	frame:SetFrameStrata("DIALOG")
	frame:CreateTitleRegion():SetAllPoints(frame)

	frame:SetScript("OnHide", frame.Hide)

	frame.label = frame:CreateFontString(name.."Label", "ARTWORK", "GameFontHighlightLeft")
	frame.label:SetPoint("TOPLEFT", 24, -20)

	frame.edit = CreateFrame("EditBox", name.."Edit", frame)
	frame.edit:SetWidth(144)
	frame.edit:SetHeight(18)
	frame.edit:SetTextInsets(3, 3, 2, 2)
	frame.edit:SetFontObject("GameFontHighlightLeft")
	frame.edit:SetBackdrop({ bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background", tile = true, tileSize = 16, edgeFile = "", edgeSize = 0, insets = {left = 0, right = 0, top = 0, bottom = 0 }, })
	frame.edit:SetPoint("LEFT", 26, 4)
	frame.edit:SetPoint("RIGHT", -26, 4)

	local edge = CreateFrame("Frame", name.."EditEdge", frame.edit)
	edge:SetBackdrop({ bgFile = "", tile = true, tileSize = 16, edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", edgeSize = 16, insets = {left = 5, right = 5, top = 5, bottom = 5 }, })
	edge:SetBackdropBorderColor(0.75, 0.75, 0.75, 0.75)
	edge:SetPoint("TOPLEFT", -4, 4)
	edge:SetPoint("BOTTOMRIGHT", 4, -4)
	edge:SetScale(0.75)	

	frame.edit:SetScript("OnEnterPressed", function(self) self:GetParent().ok:Click() end)
	frame.edit:SetScript("OnEscapePressed", function(self) self:GetParent().cancel:Click() end)
	frame.edit:SetScript("OnEditFocusGained", function(self) self:HighlightText() end)

	frame.ok = CreateFrame("Button", name.."OK", frame, "UIPanelButtonTemplate")
	frame.ok:SetWidth(85)
	frame.ok:SetHeight(22)
	frame.ok:SetText(OKAY)
	frame.ok:SetPoint("BOTTOMRIGHT", frame, "BOTTOM", -12, 20)
	frame.ok:SetScript("OnClick", function(self)
		local parent = self:GetParent()
		if type(parent.OnOK) == "function" and parent:OnOK(parent.edit:GetText()) then
			parent.edit:HighlightText()
		else
			parent:Hide()
		end		
	end)

	frame.cancel = CreateFrame("Button", name.."Cancel", frame, "UIPanelButtonTemplate")
	frame.cancel:SetWidth(85)
	frame.cancel:SetHeight(22)
	frame.cancel:SetText(CANCEL)
	frame.cancel:SetPoint("BOTTOMLEFT", frame, "BOTTOM", 12, 20)
	frame.cancel:SetScript("OnClick", function(self) self:GetParent():Hide() end)

	return frame
end

-- Extracts item id
local function GetItemId(bag, slot)
	local lnk
	if slot then
		lnk = GetContainerItemLink(bag, slot)
	else
		lnk = GetInventoryItemLink("player", bag)
	end

	if lnk then
		local _, _, id = string.find(lnk, "item:(%d+).+%[(.+)%]")
		return tonumber(id)
	end
end

-- Use a container item by id
local function UseContainerItemById(containers, itemId)
	if itemId and itemId > 0 then
		local bag, slot
		for _, bag in ipairs(containers) do
			for slot = 1, GetContainerNumSlots(bag) do
				local id = GetItemId(bag, slot)
				if id == itemId then
					-- You won't be in combat while visiting a bank, will you? UseContainerItem is quite safe here...
					UseContainerItem(bag, slot)
					return bag, slot
				end
			end
		end
	end
end

-- Moves a set into bank, or take it out of bank
local function MoveSet(self, toBank)
	if bankOpened then
		local containers = toBank and BAGS or BANKS	
		local setids = GetEquipmentSetItemIDs(rClickedSet)
		local id
		for _, id in pairs(setids) do
			if id and id ~= 0 then
				UseContainerItemById(containers, id)
			end
		end
	end
end

-- Update checked/unchecked status for all set buttons
local function UpdateSetButtons()
	local i
	for i = 1, MAX_EQUIPMENT_SETS_PER_PLAYER do
		local button = getglobal("GearSetButton"..i)
		if button then
			if button.name and button.name ~= "" and button.name == activeSet then
				if checkFrame.parent ~= button then
					checkFrame.parent = button
					checkFrame:ClearAllPoints()
					checkFrame:SetPoint("CENTER", button, "CENTER")
					checkFrame:Show()
				end
			else
				if checkFrame.parent == button then
					checkFrame.parent = nil
					checkFrame:Hide()
				end
			end

			--button:SetChecked(button.name and button.name ~= "" and button.name == activeSet)
			if button.name and button.name ~= "" then
				button.hotkey:Show()
			else
				button.hotkey:Hide()
			end
		end
	end
end

-- Texture to icon index
local function GetIconIDFromTexture(iconTexture)
	RefreshEquipmentSetIconInfo()
	local numIcons = GetNumMacroIcons()
	local i
	for i = INVSLOT_FIRST_EQUIPPED, INVSLOT_LAST_EQUIPPED do
		if GetInventoryItemTexture("player", i) then
			numIcons = numIcons + 1
		end
	end

	for i = 1, numIcons do
		local texture, id = GetEquipmentSetIconInfo(i)
		if texture == iconTexture then 
			return id
		end
	end

	return 1
end

-- Bind a set to a talent group
local function BindToTalent(id)
	local set = (db.talentBind[id] ~= rClickedSet) and rClickedSet or nil
	if set then
		local i
		for i = 1, 2 do
			if db.talentBind[i] == set then
				db.talentBind[i] = nil
			end
		end
	end
	db.talentBind[id] = set
end

-- Check whether a set is currently worn
local function CheckSetWorn(equipped, name)
	if not name then
		return
	end

	local ids = GetEquipmentSetItemIDs(name)
	local k, v
	for k, v in pairs(ids) do
		if v and v ~= 0 and equipped[k] ~= v then
			return
		end
	end
	return 1
end

-- Find the currently worn set and update the UI
local function UpdateWornSet()
	local equipped = {}
	local i, id, name
	for i = 0, 19 do
		equipped[i] = GetItemId(i) or 0
	end

	for i = 1, GetNumEquipmentSets() do
		local setName = GetEquipmentSetInfo(i)
		if CheckSetWorn(equipped, setName) then
			id, name = i, setName
			break
		end		
	end

	if name ~= activeSet then
		CloseDropDownMenus()
		local prevName = activeSet
		activeId, activeSet = id, name
		UpdateSetButtons()
		if name then
			PrintMessage(string.format(L["wore set"], name))
		end

		-- Notify other addons, if any interested
		GearManagerEx_OnActiveSetChanged(name, prevName)
	end
end

-- Directly save a set
local function SaveSet()
	if rClickedSet then		
		PrintMessage(string.format(L["set saved"], rClickedSet))
		SaveEquipmentSet(rClickedSet, GetIconIDFromTexture(rClickedSetIcon))
	end
end

local function RenameSet()
	if not inputDialog then
		inputDialog = CreateInputDialog("GearManagerExInputDialog", GearManagerDialog)		
		inputDialog.OnOK = function(self, name)
			name = string.trim(name or "")
			if name == "" then
				return 1
			end

			if GetEquipmentSetInfoByName(name) then
				PrintMessage(string.format(L["name exists"], name), 1, 0, 0)
				return 1
			end
			
			PrintMessage(string.format(L["set renamed"], self.setName, name))
			RenameEquipmentSet(self.setName, name)
		end
	end

	inputDialog.label:SetText(string.format(L["label text"], rClickedSet))
	inputDialog.setName = rClickedSet
	inputDialog:Show()
end

local function DeleteSet()
	local frame = StaticPopup_Show("CONFIRM_DELETE_EQUIPMENT_SET", rClickedSet)
	frame.data = rClickedSet
end

-- The background message handler frame
local manager = CreateFrame("Frame")
manager:Hide()
manager:RegisterEvent("VARIABLES_LOADED")
manager:RegisterEvent("PLAYER_ENTERING_WORLD")
manager:RegisterEvent("UPDATE_BINDINGS")
manager:RegisterEvent("BANKFRAME_OPENED")
manager:RegisterEvent("BANKFRAME_CLOSED")
manager:RegisterEvent("ACTIVE_TALENT_GROUP_CHANGED")
manager:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
manager:RegisterEvent("EQUIPMENT_SETS_CHANGED")

manager:SetScript("OnEvent", function(self, event, id)
	if event == "VARIABLES_LOADED" then
		SetCVar("equipmentManager", 1)
		GearManagerToggleButton:Show()

		if type(GearManagerExDB) ~= "table" then
			GearManagerExDB = {}
		end

		db = GearManagerExDB

		if type(db.talentBind) ~= "table" then
			db.talentBind = {}
		end		
		
		DEFAULT_CHAT_FRAME:AddMessage("|cffffff78GearManagerEx "..VERSION.."|r by Abin loaded.")

	elseif event == "UPDATE_BINDINGS" then
		local i
		for i = 1, MAX_EQUIPMENT_SETS_PER_PLAYER do
			local button = getglobal("GearSetButton"..i)
			if button and button.hotkey then
				local key = GetBindingKey("GEARMANAGEREX_WEARSET"..i)
				button.hotkey:SetText(GetBindingText(key, "KEY_", 1))
			end
		end
	elseif event == "BANKFRAME_OPENED" then
		bankOpened = 1
		CloseDropDownMenus()
	elseif event == "BANKFRAME_CLOSED" then
		bankOpened = nil
		CloseDropDownMenus()
	elseif event == "ACTIVE_TALENT_GROUP_CHANGED" then
		local set = db.talentBind[id]
		if set then
			EquipmentManager_EquipSet(set)
		end
	elseif event == "PLAYER_ENTERING_WORLD" then
		self:UnregisterEvent("PLAYER_ENTERING_WORLD")
		UpdateWornSet()
		self:Show()
	elseif event == "PLAYER_EQUIPMENT_CHANGED" then		
		self.needCheck = 1 -- Highly concurrent events, use post-processing to avoid performance drop...
	elseif event == "EQUIPMENT_SETS_CHANGED" then
		UpdateWornSet()
	end
end)

manager:SetScript("OnUpdate", function(self, elapsed)
	if self.needCheck then
		self.needCheck = nil
		UpdateWornSet()
	end
end)

-- The drop down setMenu for set buttons
local setMenu = CreateFrame("Button", "GearManagerExDropDownButtonSetMenu", GearManagerDialog, "UIDropDownMenuTemplate")
setMenu.point = "TOPLEFT"
setMenu.relativePoint = "BOTTOMLEFT"
setMenu.xOffset = -4
setMenu.yOffset = -4

-- Our menu items for advanced set operations
UIDropDownMenu_Initialize(setMenu, function()
	UIDropDownMenu_AddButton({ text = rClickedSet, isTitle = 1, icon = rClickedSetIcon })
	UIDropDownMenu_AddButton({ text = L["save set"], disabled = activeSet, func = SaveSet })
	UIDropDownMenu_AddButton({ text = L["delete set"], func = DeleteSet })
	UIDropDownMenu_AddButton({ text = L["rename set"], func = RenameSet })
	UIDropDownMenu_AddButton({ text = L["put into bank"], disabled = not bankOpened, func = MoveSet, arg1 = 1 })
	UIDropDownMenu_AddButton({ text = L["take from bank"], disabled = not bankOpened, func = MoveSet })
	if GetNumTalentGroups() > 1 then
		UIDropDownMenu_AddButton({ text = L["bind to"]..TALENT_SPEC_PRIMARY, checked = db.talentBind[1] == rClickedSet, func = function() BindToTalent(1) end })
		UIDropDownMenu_AddButton({ text = L["bind to"]..TALENT_SPEC_SECONDARY, checked = db.talentBind[2] == rClickedSet, func = function() BindToTalent(2) end })
	end
	UIDropDownMenu_AddButton({ text = CLOSE })
end, "MENU")

--------------------------------------------------------------
-- Create Hotkey text for GearSetButton1-10
--------------------------------------------------------------
local i
for i = 1, MAX_EQUIPMENT_SETS_PER_PLAYER do
	local button = getglobal("GearSetButton"..i)
	if button then
		button.hotkey = button:CreateFontString(button:GetName().."HotKey", "OVERLAY", "NumberFontNormalSmallGray")
		button.hotkey:SetJustifyH("RIGHT")
		button.hotkey:SetPoint("TOPLEFT", 2, -2)
		button.hotkey:SetPoint("TOPRIGHT", -2, -2)
		
		-- Hook "OnMouseDown" for every button to show the drop down menu
		button:HookScript("OnMouseDown", function(self, button)			
			if prevDropDown ~= self then
				CloseDropDownMenus()
			end

			if button == "RightButton" and self.name and self.name ~= "" then
				prevDropDown = self
				setMenu.relativeTo = self
				rClickedSet = self.name
				rClickedSetIcon = self.icon:GetTexture()
				ToggleDropDownMenu(nil, nil, setMenu)
			end

			GameTooltip:Hide()
		end)		
	end
end

-- Colorize set tooltip texts with item quality, instead of plain white
local function ReplaceTooltipTextColor(fs)
	if fs and fs:GetText() then
		local _, _, quality = GetItemInfo(fs:GetText())
		if quality then
			local r, g, b = GetItemQualityColor(quality)
			fs:SetTextColor(r, g, b)
		end
	end
end

-- Hook GameTooltip if it's displaying an equipment set
GameTooltip:HookScript("OnTooltipSetEquipmentSet", function(self)
	local i
	for i = 1, self:NumLines() do
		ReplaceTooltipTextColor(getglobal("GameTooltipTextLeft"..i))
		ReplaceTooltipTextColor(getglobal("GameTooltipTextRight"..i))
	end
end)

-- Prompt the player that he can right-click a set for more operations
hooksecurefunc("GearSetButton_OnEnter", function(self)
	if self.name and self.name ~= "" and GameTooltipTextLeft1:GetText() == self.name then		
		GameTooltip:AddLine(" ")
		GameTooltip:AddDoubleLine(L["set hotkey"], self.hotkey:GetText(), nil, nil, nil, 1, 1, 1)		
		GameTooltip:AddLine(L["tooltip prompt"], 0, 1, 0, 1)
		GameTooltip:Show()
	end
end)

-- Update the checked/unchecked stats for every set button
hooksecurefunc("GearManagerDialog_Update", function()
	UpdateSetButtons()
end)

--------------------------------------------------------------
-- Quick strip
--------------------------------------------------------------

local DURASLOTS = { 16, 17, 18, 5, 7, 1, 3, 8, 10, 6, 9 } -- Inventory slots that possibly have durabilities
local lastOpt = 0 -- Last operated time

local function IsEquippableSlot(slot)
	return slot > 15 or not InCombatLockdown()
end

local function GetItemLocked()
	local bag, slot
	for bag = 0, 4 do
		for slot = 1, GetContainerNumSlots(bag) do
			if select(3, GetContainerItemInfo(bag, slot)) then
				return bag, slot
			end
		end
	end
end

-- Find available bag slots to put items into
local function GetAvailableBags()
	local bags = {}
	local i
	for i = 0, 4 do		
		local slots, family = GetContainerNumFreeSlots(i)
		bags[i] = (family == 0) and slots or 0
	end
	return bags
end

local function GetFirstAvailableBag(bags)
	local i
	for i = 0, 4 do
		if bags[i] > 0 then
			return i
		end
	end
end

local function FindContainerItem(lnk, allowLocked)
	if type(lnk) == "string" then
		local bag, slot
		for bag = 0, 4 do
			for slot = 1, GetContainerNumSlots(bag) do
				local _, _, locked, _, _, _, link = GetContainerItemInfo(bag, slot)
				if (allowLocked or not locked) and link == lnk then
					return bag, slot
				end
			end
		end
	end	
end

local function VerifyStrippedDB()
	local hasContents
	if type(db.stripped) == "table" then
		local inv, lnk
		for inv, lnk in pairs(db.stripped) do
			if type(inv) == "number" and not GetInventoryItemLink("player", inv) and FindContainerItem(lnk, 1) then
				hasContents = 1
			else
				db.stripped[inv] = nil
			end
		end
	end
	
	if not hasContents then
		db.stripped = nil
	end

	return hasContents
end

-- Strip off all items those have durabilities
local function StripOff()
	local bags = GetAvailableBags()
	local count = 0
	local i
	for i = 1, table.getn(DURASLOTS) do
		local inv = DURASLOTS[i]
		local lnk = GetInventoryItemLink("player", inv)
		if lnk and IsEquippableSlot(inv) and GetInventoryItemDurability(inv) then
			local bag = GetFirstAvailableBag(bags)
			if bag then
				bags[bag] = bags[bag] - 1
				PickupInventoryItem(inv)
				if CursorHasItem() then					
					if bag == 0 then
						PutItemInBackpack()
					else
						PutItemInBag(19 + bag)
					end
						
					if not db.stripped then
						db.stripped = {}
					end

					db.stripped[inv] = lnk
					count = count + 1
				end
			else
				PrintMessage(ERR_INV_FULL, 1, 0, 0)
				return count
			end
		end
	end
	return count
end

-- Wear back whatever we stripped off through a previous call to StripOff()
local function WearBack()
	local count = 0
	local inv, lnk
	for inv, lnk in pairs(db.stripped) do
		if IsEquippableSlot(inv) then
			local bag, slot = FindContainerItem(lnk)
			if bag and slot then
				PickupContainerItem(bag, slot)
				if CursorHasItem() then
					EquipCursorItem(inv)
					count = count + 1
				end
			end
		end
	end
	db.stripped = nil
	return count
end

------------------------------------------------------------
-- Globals
------------------------------------------------------------

-- The purpose of this function is to give other addons a chance to get notified when the "active set" changes,
-- it is called when either a set is completely worn, or a previously worn set becomes no longer complete. 
-- "currentName" - the current active set name, can be nil.
-- "previousName" - the previous active set name, can be nil.
-- For example, if "currentName" is nil and "previousName" is not, it indicates that some piece(s) of the set has
-- just been taken off.
-- Other addons may catch this event by secure-hooking "GearManagerEx_OnActiveSetChanged".
function GearManagerEx_OnActiveSetChanged(currentName, previousName)
	-- Dummy
end

-- Get the current active set info: index, name, icon
function GearManagerEx_GetActiveSet()
	if activeId then
		local name, icon, internalId = GetEquipmentSetInfo(activeId)
		return activeId, name, icon, internalId
	end	
end

-- Show/hide GearManagerDialog
function GearManagerEx_ToggleUI()
	if GearManagerDialog:IsVisible() then
		GearManagerDialog:Hide()
	else
		if not PaperDollFrame:IsVisible() then
			ToggleCharacter("PaperDollFrame")
		end
		GearManagerDialog:Show()
	end
end

-- Strip/wear
function GearManagerEx_QuickStrip()
	if UnitIsDeadOrGhost("player") or not HasFullControl() or GetItemLocked() then
		UIErrorsFrame:AddMessage(ERR_CLIENT_LOCKED_OUT, 1.0, 0.1, 0.1, 1.0)
		return
	end

	if GetTime() - lastOpt < 2 then
		PrintMessage(L["too fast"], 1, 0, 0)
		return
	end

	ClearCursor()
	if VerifyStrippedDB() then
		PrintMessage(string.format(L["wore back"], WearBack()))
	else
		PrintMessage(string.format(L["stripped off"], StripOff()))
	end
	lastOpt = GetTime()
end

-- Equip a set by index
function GearManagerEx_EquipSetByIndex(id)
	if type(id) == "number" then
		local name = GetEquipmentSetInfo(id)
		if name then
			EquipmentManager_EquipSet(name)
		end
	end	
end

------------------------------------------------------------
-- Binding texts
------------------------------------------------------------

BINDING_HEADER_GEARMANAGEREX_TITLE = "GearManagerEx"
BINDING_NAME_GEARMANAGEREX_TOGGLEUI = L["open"]..EQUIPMENT_MANAGER
BINDING_NAME_GEARMANAGEREX_QUICKSTRIP = L["quick strip"]
BINDING_NAME_GEARMANAGEREX_WEARSET1 = L["wear set"].." 1"
BINDING_NAME_GEARMANAGEREX_WEARSET2 = L["wear set"].." 2"
BINDING_NAME_GEARMANAGEREX_WEARSET3 = L["wear set"].." 3"
BINDING_NAME_GEARMANAGEREX_WEARSET4 = L["wear set"].." 4"
BINDING_NAME_GEARMANAGEREX_WEARSET5 = L["wear set"].." 5"
BINDING_NAME_GEARMANAGEREX_WEARSET6 = L["wear set"].." 6"
BINDING_NAME_GEARMANAGEREX_WEARSET7 = L["wear set"].." 7"
BINDING_NAME_GEARMANAGEREX_WEARSET8 = L["wear set"].." 8"
BINDING_NAME_GEARMANAGEREX_WEARSET9 = L["wear set"].." 9"
BINDING_NAME_GEARMANAGEREX_WEARSET10 = L["wear set"].." 10"